using System;
using System.Collections.Generic;
using System.Data;
using JetBrains.Annotations;
using UnityEngine;

[RequireComponent(typeof(MainLine))]
public class LineTransform : MonoBehaviour, IResettable
{
    [DataMember] public Action<LineEventArgs> onLeavingLand;
    [DataMember] public Action<LineEventArgs> onLanded;
    [DataMember] public Transform lineBodyPrefab;
    [DataMember] public Func<bool> controlCriteria;
    [DataMember, Range(1, 1000)] public int maxBodyCount = 200;

    [DataMember, UsedImplicitly]
    private Vector3 Position
    {
        get => m_transform.position;
        set => m_transform.position = value;
    }

    [DataMember, UsedImplicitly]
    private Quaternion Rotation
    {
        get => m_transform.rotation;
        set => m_transform.rotation = value;
    }

    [DataMember] private MainLine m_line;
    [DataMember] private Transform m_transform;
    [DataMember] private bool m_playing;
    [DataMember] private bool m_landed = false;
    [DataMember] private int m_state;
    [DataMember] private bool m_mouseDown;
    [DataMember] private Vector3 m_lastTurnPosition;
    [DataMember] private Transform m_lastBody;
    private readonly Queue<Transform> m_bodyQueue = new Queue<Transform>();
    [DataMember] private Action m_bufferAction;
    [DataMember] private bool m_justStarted;

    public List<Vector3> directions = new List<Vector3> {Vector3.zero, new Vector3(0, 90, 0)};

    [DataMember, UsedImplicitly]
    private List<Vector3> Directions
    {
        get => directions;
        set => directions = new List<Vector3>(value);
    }

    private void Start()
    {
        m_line = GetComponent<MainLine>();
        m_transform = transform;
        controlCriteria += ControlCriteria;
        onLanded += Land;
        onLeavingLand += CutLineBody;
        if (directions == null || directions.Count <= 0)
        {
            DebugManager.LogError("Too few orientations.");
        }

        if (!lineBodyPrefab)
        {
            DebugManager.LogError("Line Body Prefab Missing.");
        }

        Singleton<GameController>.Instance.OnGameStateChanged += OnGameStateChanged;
        Singleton<InputManager>.Instance.BindMouse(0, OnMouseButtonDown);

        this.SaveData();
    }

    [DebugManager.CriticalDelegateAttribute]
    private bool ControlCriteria()
    {
        return m_line.Landed;
    }

    private void OnMouseButtonDown(InputManager.KeyType type)
    {
        if (type == InputManager.KeyType.Down)
        {
            m_mouseDown = true;
        }
    }

    private void OnGameStateChanged(GameState state)
    {
        m_bufferAction += () =>
        {
            m_playing = state == GameState.Playing;
            if (!m_playing) return;
            m_justStarted = true;
            m_lastTurnPosition = m_transform.position;
        };
    }

    [DebugManager.CriticalDelegateAttribute]
    private void CutLineBody(LineEventArgs args)
    {
        m_lastBody = null;
        SetLastTurnPosition();
    }

    [DebugManager.CriticalDelegateAttribute]
    private void Land(LineEventArgs args)
    {
        GenerateBody();
        SetLastTurnPosition();
    }


    private void Update()
    {
        m_bufferAction?.Invoke();
        m_bufferAction = null;
        if (!m_playing) return;
        if (m_landed != m_line.Landed)
        {
            if (m_line.Landed)
            {
                onLanded.Invoke(m_line.GetLineEventArgs());
            }
            else
            {
                onLeavingLand.Invoke(m_line.GetLineEventArgs());
            }
        }

        if (m_mouseDown)
        {
            m_mouseDown = false;
            var criteriaMet = controlCriteria.IsAllTrue();
            if (criteriaMet)
            {
                GenerateBody();
            }

            if (!m_justStarted && criteriaMet)
            {
                Turn();
            }
            else
            {
                m_justStarted = false;
            }
        }

        m_landed = m_line.Landed;
        Move();
        UpdateBody();
    }

    private void SetLastTurnPosition()
    {
        m_lastTurnPosition = m_transform.position;
    }

    private void Turn()
    {
        SetLastTurnPosition();
        m_state = (m_state + 1) % directions.Count;
        m_transform.localRotation = Quaternion.Euler(directions[m_state]);
    }

    private void GenerateBody()
    {
        if (m_bodyQueue.Count < maxBodyCount)
        {
            m_lastBody = Instantiate(lineBodyPrefab, m_transform.position, Quaternion.identity);
            m_bodyQueue.Enqueue(m_lastBody);
        }
        else
        {
            m_lastBody = m_bodyQueue.Dequeue();
            m_bodyQueue.Enqueue(m_lastBody);
        }
    }

    private void Move()
    {
        m_transform.Translate(Vector3.forward * (m_line.Speed * Singleton<GameController>.Instance.DeltaTime));
    }

    private void UpdateBody()
    {
        if (!m_lastBody) return;
        Vector3 position = m_transform.position;
        m_lastBody.position = (m_lastTurnPosition + position) / 2f;
        m_lastBody.localScale = lineBodyPrefab.localScale + (position - m_lastTurnPosition);
    }

    public void Reset()
    {
        if (!this.SetData())
        {
            DebugManager.LogError("Errors occurred when LineTransform attempted to reload data");
        }

        foreach (Transform body in m_bodyQueue)
        {
            Destroy(body.gameObject);
        }

        m_bodyQueue.Clear();
    }
}